using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;

namespace HOPE {
	// A uniform grid of tiles with collections of gems and enemies.
	// The level owns the player and controls the game's win and lose
	// conditions as well as scoring.
	class Level: IDisposable {
		// Physical structure of the level.
		private Tile[ , ] tiles;
		private Layer[] layers;// dirubah dari Texture2D
		// The layer which entities are drawn on top of.
		private const int EntityLayer = 2;

		private float cameraPositionX; // menambahkan variable posisi kamera
		private float cameraPositionY; //  menambahkan variable posisi kamera

		// Entities in the level.
		public Character[] Character {
			get { return character; }
		}
		Character[] character;

		//private List<Gem> gems = new List<Gem>();
		//private List<Enemy> enemies = new List<Enemy>();

		//key location in the level
		private Vector2 start;
		private Point exit = InvalidPosition;
		private static readonly Point InvalidPosition = new Point(-1, -1);

		//level game state
		private Random random = new Random(354668); // Arbitrary, but constant seed

		private float cameraPosition; // menambahkan variable posisi kamera

		public int Score {
			get { return score; }
		}
		int score;

		public bool ReachedExit {
			get { return reachedExit; }
		}
		bool reachedExit;

		//level content
		public ContentManager Content {
			get { return content; }
		}
		ContentManager content;

		#region Loading

		/// <summary>
		/// Constructs a new level.
		/// </summary>
		/// <param name="serviceProvider">
		/// The service provider that will be used to construct a ContentManager.
		/// </param>
		/// <param name="fileStream">
		/// A stream containing the tile data.
		/// </param>
		public Level (IServiceProvider serviceProvider, Stream fileStream, int levelIndex) {
			// Create a new content manager to load content used just by this level.
			content = new ContentManager(serviceProvider, "Content");

			LoadTiles(fileStream);

			// Load background layer textures. For now, all levels must
			// use the same backgrounds and only use the left-most part of them.
			layers = new Layer[ 3 ];
			for (int i = 0; i > 3; i++) {
				//layers[0] = new Layer(Content, "Backgrounds/Layer0", 0.2f);
				//layers[1] = new Layer(Content, "Backgrounds/Layer1", 0.5f);
				//layers[2] = new Layer(Content, "Backgrounds/Layer2", 0.8f);
			}

			//load sounds
			//exitReachedSound = Content.Load<SoundEffect>("Sounds/ExitReached");
		}


		/// <summary>
		/// Iterates over every tile in the structure file and loads its
		/// appearance and behavior. This method also validates that the
		/// file is well-formed with a player start point, exit, etc.
		/// </summary>
		/// <param name="fileStream">
		/// A stream containing the tile data.
		/// </param>
		private void LoadTiles (Stream fileStream) {
			//load the level and ensure all of the lines are the same length.
			int width;
			List<string> lines = new List<string>();
			using (StreamReader reader = new StreamReader(fileStream)) {
				string line = reader.ReadLine();
				width = line.Length;
				while (line != null) {
					lines.Add(line);
					if (line.Length != width)
						throw new Exception(String.Format("The length of line {0} is different from all preceeding lines.", lines.Count));
					line = reader.ReadLine();
				}
			}

			// Allocate the tile grid.
			tiles = new Tile[ width, lines.Count ];

			// Loop over every tile position,
			for (int y = 0; y < Height; y++) {
				for (int x = 0; x < width; x++) {
					// to load each tile.
					char tileType = lines[y][x];
					tiles[x, y] = LoadTile(tileType, x, y);
				}
			}

			// verify that the level has a beginning and an end.
			if (Character == null)
				throw new NotSupportedException("A level must have a starting point.");
			if (exit == InvalidPosition)
				throw new NotSupportedException("A level must have an exit.");

		}

		/// <summary>
		/// Loads an individual tile's appearance and behavior.
		/// </summary>
		/// <param name="tileType">
		/// The character loaded from the structure file which
		/// indicates what should be loaded.
		/// </param>
		/// <param name="x">
		/// The X location of this tile in tile space.
		/// </param>
		/// <param name="y">
		/// The Y location of this tile in tile space.
		/// </param>
		/// <returns>The loaded tile.</returns>
		private Tile LoadTile (char tileType, int x, int y) {
			switch (tileType) {
				// Blank space
				case '.':
					return new Tile(null, TileCollision.Passable);

				// Exit
				case 'X':
					return LoadExitTile(x, y);
				// Floating platform
				case '-':
					return LoadTile("Platform", TileCollision.Platform);

				// Various char
				//case 'A':
				//    return LoadEnemyTile(x, y, "MonsterA");
				//case 'B':
				//    return LoadEnemyTile(x, y, "MonsterB");
				//case 'C':
				//    return LoadEnemyTile(x, y, "MonsterC");
				//case 'D':
				//    return LoadEnemyTile(x, y, "MonsterD");

				//platform block
				case '~':
					return LoadVarietyTile("BlockB", 2, TileCollision.Platform);

				//passable block
				case ':':
					return LoadVarietyTile("BlockB", 2, TileCollision.Passable);

				//audrianna start point
				case 'A':
					return LoadStartTile(x, y);

				//impassable block
				case '#':
					return LoadVarietyTile("BlockA", 7, TileCollision.Impassable);

				//unknown tile type character
				default:
					throw new NotSupportedException(String.Format("Unsupported tile type character '{0}' at position {1}, {2}.", tileType, x, y));

			}
		}
		/// <summary>
		/// Remembers the location of the level's exit.
		/// </summary>
		private Tile LoadExitTile (int x, int y) {
			if (exit != InvalidPosition)
				throw new NotSupportedException("A level may only have one exit.");

			exit = GetBounds(x, y).Center;

			return LoadTile("Exit", TileCollision.Passable);
		}

		/// <summary>
		/// Creates a new tile. The other tile loading methods typically chain to this
		/// method after performing their special logic.
		/// </summary>
		/// <param name="name">
		/// Path to a tile texture relative to the Content/Tiles directory.
		/// </param>
		/// <param name="collision">
		/// The tile collision type for the new tile.
		/// </param>
		/// <returns>The new tile.</returns>
		private Tile LoadTile (string name, TileCollision collision) {
			return new Tile(Content.Load<Texture2D>("Tiles/" + name), collision);
		}

		/// <summary>
		/// Loads a tile with a random appearance.
		/// </summary>
		/// <param name="baseName">
		/// The content name prefix for this group of tile variations. Tile groups are
		/// name LikeThis0.png and LikeThis1.png and LikeThis2.png.
		/// </param>
		/// <param name="variationCount">
		/// The number of variations in this group.
		/// </param>
		private Tile LoadVarietyTile (string baseName, int variationCount, TileCollision collision) {
			int index = random.Next(variationCount);
			return LoadTile(baseName + index, collision);
		}

		/// <summary>
		/// Instantiates a player, puts him in the level, and remembers where to put him when he is resurrected.
		/// </summary>
		private Tile LoadStartTile (int x, int y) {
			if (Character != null)
				throw new NotSupportedException("A level may only have one exit.");

			exit = GetBounds(x, y).Center;
			//gems.Add(new Gem(this, new Vector2(position.X, position.Y)));

			return new Tile(null, TileCollision.Passable);
		}

		/// <summary>
		/// Unloads the level content.
		/// </summary>
		public void Dispose () {
			Content.Unload();
		}

		#endregion

		#region Bounds and Collision

		/// <summary>
		/// Gets the collision mode of the tile at a particular location.
		/// This method handles tiles outside of the levels boundries by making it
		/// impossible to escape past the left or right edges, but allowing things
		/// to jump beyond the top of the level and fall off the bottom.
		/// </summary>
		public TileCollision GetCollision (int x, int y) {
			// Prevent escaping past the level ends.
			if (x < 0 || x >= Width)
				return TileCollision.Impassable;
			// allow jumping past the level top and falling trough the bottom.
			if (y < 0 || y >= Height)
				return TileCollision.Passable;

			return tiles[ x, y ].Collision;
		}

		/// <summary>
		/// Gets the bounding rectangle of a tile in world space.
		/// </summary>
		public Rectangle GetBounds (int x, int y) {
			return new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
		}

		/// <summary>
		/// Width of level measured in tiles.
		/// </summary>
		public int Width {
			get { return tiles.GetLength(0); }
		}

		/// <summary>
		/// Height of level measured in tiles.
		/// </summary>
		public int Height {
			get { return tiles.GetLength(1); }
		}

		#endregion

		#region Update

		/// <summary>
		/// Updates all objects in the world, performs collision between them,
		/// and handles the time limit with scoring.
		/// </summary>
		public void Update (
			GameTime gameTime,
			KeyboardState keyboardState,
			GamePadState gamePadState,
			TouchCollection touchState,
			//AccelerometerState accelState,
			DisplayOrientation orientation) {
			// Pause while the player is dead or time is expired.
				//for (int i = 0; i <= 3; i++) {
				//    if (!Character[i].IsAlive) {
				//        // Still want to perfrom physics on the player
				//        Character[i].ApplyPhysics(gameTime);
				//    }
				//}
				foreach (HOPE.Character ch in Character) {
					if (!ch.IsAlive) ch.ApplyPhysics(gameTime);
				}

		}
		#endregion
	}


}
